/* 
Juan Calderin
Project 1: Write your own Shell
COP4610
9/26/11
*/

#define _GNU_SOURCE	//	to use the GNU extensions (asprintf)

#include <stdio.h>		// for standard library functions
#include <stdlib.h>
#include <unistd.h>
#include <string.h>		// for string parsing
#include <ctype.h>
#include <sys/types.h>
#include <fcntl.h>
#include <time.h>
#include <sys/wait.h>	// for wait function

/* Global Variables */

int size = 80,	// buffer size
	bg=0,		// flag --> '0' if not a background process, '1' if it is.
	redir = 0,	// redirection var: no, output or input (0,1 or 2)
	numpipes,	// to hold number of pipes
	pipelocation[3],	//
	thePID;
char hostname[128],
	str[80],	// entire typed command
	pwd[128],	// to store and update PWD
	* justcmd,	// contains only the command name, e.g. "ls"
	* full_path = NULL,	// start with an empty string
	* arguments[128], * arguments2[128], * arguments3[128], * arguments4[128],
	* filename,		// file to write/read in output/input redirection
	* time1,	// initial time
	* time2;	// final time

char * startTime[100],
        * endTime[100],
        * command[100],
        * status[100];
int size2=0;


// ------------------------------------------------------------------------- //

/*Function prototypes in a somewhat order of run*/
void readline(char *, int),	// read user input
	getcmd();	// get the first argument (the command)
	
int getArgs(char *[128]);	// tokenize the arguments, returns 0 if sucessful or error code
void prompt();	// display the prompt
int check(char *, char *);	// check that the 
void executecmd(char *);	// executes the command using execv
int parsePath(char *);	// parse the PATH environment variable, returns 1 if successful
void cd();	// implements the "cd" command
void echo();	// implements the "echo" command
int BuiltIn(char * cmd);	// checks if the command is built-in
void exitcmd();	// implements the "exit" command
void which();	// implements the "which" command
void viewproc();	// implements viewproc built-in command
int checkBg;	// returns 1 if background process;
void outputredir(char *fname);	// output redirection function that writes to a file
void inputredir(char *fname);	// input redirection function that writes to a file
int cleanArgs(int ,char **);	// cleanup the args
void getPipes();
void addHistory(char * command);	// to add to the history
void displayHistory();		// to display the history
void getTime(int );		// to get local time

// ------------------------------------------------------------------------- //

void readline(char *line, int size) {	// read user input into a string
   if ( fgets(line, size, stdin) ) {		// get standard input
	   char *newline = strchr(line, '\n'); // locate carriage return character
	   if ( newline )	// if a carriage return character was found
		   *newline =  '\0'; // get rid of carriage return character
   }
   
}	// end of readline

// ------------------------------------------------------------------------- //

void getcmd(){	// gets the raw command, without any arguments
	char * temp = strdup(str);	// to preserve original str
	justcmd = strtok (temp, " ");	// get the first word (command)
}	// end of getcmd

// ------------------------------------------------------------------------- //

int getArgs(char *args[128]) {	//  gets the arguments into the arguments array of pointers

	pipelocation[0]=0;	// restart
	pipelocation[1]=0;
	pipelocation[2]=0;
	numpipes=0;	// restart
	int i=0, pos =0, j=0;	// index variables
	redir=0;	// assume that no redirection is needed (default condition)
	args[i]=justcmd;		// get first argument
	
	while (args[i] != NULL) {	// get the rest of the arguments
		if (strchr(args[i],'>') ) {	// check if the '>' was found
			if (args[i-1] == NULL) 
				return 1;	// No first argument error code
			redir=1;	// output redirection is needed
			pos=i;	// save position where the ">" is
		}
		else if (strchr(args[i],'<')) {
			if (args[i-1] == NULL) 
				return 1;	// No first argument error code
			redir=2;	// input redirection is needed
			pos=i;	// save position where the "<" is
			
		}
		
		else if (strchr(args[i],'|') ) {	// check for pipes!
			numpipes++;		// increase num pipes
			pipelocation[j++]=i;
		}
		
		args [++i]=strtok (NULL, " ");	// get "words" (separated with spaces)
	}
	
	if (i>1) {	// more than one argument
		if(!strcmp(args[i-1],"&")) {// look for "&"
			bg=WNOHANG;	// flag variable to execute program in background mode 
			args[pos]=NULL;	// get rid of ampersand as an arguments
		}
		
		return cleanArgs(pos,args);	// cleanup the args		
		
	
	}

	return 0;	// guess everything ran fine
	
}	// end of getArgs

// ------------------------------------------------------------------------- //

void getPipes() {
	
	int i =0;
	int pipecount=numpipes;
	
	if (pipecount == 3) {
		i =0;
		do {
			arguments4[i++]=arguments[++pipelocation[2]];	// get the arguments
			arguments[pipelocation[2]-1]=NULL;	// clean the arguments
			
		} while(arguments4[i] != NULL);
		
		parsePath(arguments4[0]);
		arguments4[i]=NULL;
		arguments4[0]=full_path;	// get new full path
		pipecount--;
	}
	
	if (pipecount == 2) {
		i =0;
		do {
			arguments3[i++]=arguments[++pipelocation[1]];	// get the arguments
			arguments[pipelocation[1]-1]=NULL;	// clean the arguments
		} while(arguments3[i] != NULL);
		
		parsePath(arguments3[0]);
		arguments3[i]=NULL;
		arguments3[0]=full_path;	// get new full path
		pipecount--;
	}
	
	if (pipecount == 1) {
		i =0;
		do {
			arguments2[i++]=arguments[++pipelocation[0]];	// get the arguments
			arguments[pipelocation[0]-1]=NULL;	// clean the arguments
		} while(arguments2[i-1] != NULL);
		
		parsePath(arguments2[0]);
		arguments2[i]=NULL;
		arguments2[0]=full_path;	// get new full path
	}
}

// ------------------------------------------------------------------------- //

int cleanArgs(int pos,char *args[128]) {	// cleanup the args
	if(redir != 0)	{// if we need to redirect the output
		if(!args[pos+1])
			return	2;	// file not found error code
		filename=args[pos+1];	//filename will be the last argument

		args[pos+1]=NULL;	// get rid of file name argument
		args[pos]=NULL;	// get rid of >	
	}

	return 0;	// everything ran fine

}

// ------------------------------------------------------------------------- //

void prompt(){	// display prompt to the user

	hostname[127] = '\0';	// add null character to the end
	gethostname(hostname, 128);	// store host name into char
	printf("%s@%s:%s(-_-)",getenv("USER"), hostname, pwd );	// show prompt
	readline(str, sizeof (str));	// read line from user
	getcmd();	// get the actual command without arguments
	
}	// end of prompt

// ------------------------------------------------------------------------- //
 
void cd() {		// implements built-in "cd" commmand
	
	if (arguments[1] != NULL) {	// to prevent the segmentation fault
		if(chdir(arguments[1])!=0)		// check if it changed directory
			printf("%s: No such file or directory.\n",arguments[1]);
	}
	else	// change to home folder if cd doesn't have any arguments
		chdir(getenv("HOME"));	// change directory to home directory	
	
}

// ------------------------------------------------------------------------- //

void echo() {	// implements a simple built-in "echo" commmand
	int i;	// index variable
	for(i=1;arguments[i]!=NULL;i++){	// check if there are words to print

		if(strchr(arguments[i],'$')) {	// check for environment variables
			char * atemp = strtok (arguments[i], "$");	// get the variable name
			
			if(!getenv(atemp)) {	// check that the variable exists
				printf("\nSorry \"%s\" is not a valid environment variable.",atemp);
				break;	// get out of the loop and stop checking
			}
				
			arguments[i]=malloc(strlen(getenv(atemp)));	// allocate space
			strcpy(arguments[i],getenv(atemp));	// copy the variable
		}
		
		if(i==1)	// spacing for first word
			printf("%s",arguments[i]);
		else		// spacing for the rest of the words
			printf(" %s",arguments[i]);
	}
	
	printf("\n");	// add the \n at the end
	
}	// end of echo

// ------------------------------------------------------------------------- //

void outputredir(char *fname) {	// output redirection function that writes to a file
	if(redir ==1){	// check if redir flag is for output redirection
		open(fname, O_RDWR | O_CREAT | O_TRUNC, 0644);	// create file with 644 permissions
		close(1);	// don't show standard output: file descriptor 1
		dup(3);		// duplicate file descriptor
		close(3);	// close file descriptor
	}
}	// end of outputredir

// ------------------------------------------------------------------------- //

void inputredir(char *fname) { // input redirection function 
	int filedes;	// to hold file descriptor
	
	if (redir==2) {		// check if the redir flag is for input redirection
		if ((filedes=open(fname, O_RDONLY)) == -1) {
			fprintf(stderr, "error: can't open %s\n", fname);
			exit(1);	// probably should change the function to a boolean
			}
		dup2(filedes, 0);	// duplicate stdin file descriptor
		close(filedes);		// close file descriptor
	}
   
}	// end of inputredir

// ------------------------------------------------------------------------- //

void which() {	// implementation of the 'which' built-in command
	
	getTime(1);
	
	if (arguments[1] == NULL)	// called which without any arguments
		puts("Which what?");
	else if (!strcmp(arguments[1],"exit") || !strcmp(arguments[1],"cd")
			|| !strcmp(arguments[1],"history") || !strcmp(arguments[1],"echo")
			|| !strcmp(arguments[1],"viewproc") )
		printf("%s: shell built-in command.\n",arguments[1]);	// built-in cmd
	else if (parsePath(arguments[1]))
		printf("\n%s\n",full_path);		// print full path 
	else
		printf("%s: not found inside the path.\n",arguments[1]);
	
	getTime(2);

}

// ------------------------------------------------------------------------- //

int check(char *file_path, char *cmd) {	// check that the file (command) exists

	asprintf(&full_path, "%s/%s", file_path, cmd);	// to concat strings and alloc memory

	if( access( full_path, F_OK ) != -1 )	// check file for existence
		return 1;	// file was found
	return 0;	// file doesn't exist

}	// end of check

// ------------------------------------------------------------------------- //

void executecmd(char *path) {	// execute command since it was found

	int status;
	pid_t pid, w;
	
	getTime(1);	// get initial time
	
	if ((pid = fork()) == 0 ) {
		outputredir(filename);	// write output to "filename"
		inputredir(filename);
		execv(path, arguments);
	}
	
	else
		w=waitpid(pid, &status, bg);	// wait for process with PID to c					
	
	getTime(2);	// get final time
	
	bg =0;
}	// end of executecmd

// ------------------------------------------------------------------------- //

void executecmdPipes(int numpipes) {
	
	puts("Executing pipes!");
	puts(arguments2[0]);
	
	 int p1_to_p2[2], pid, pid2, status;

  
  pipe(p1_to_p2);
  
  pid = fork();
  
  if(pid == 0){
    close(0);
    dup(p1_to_p2[0]);
    close(p1_to_p2[1]);
    close(p1_to_p2[0]);
    
    execv(arguments2[0], arguments2);
  }
  
  pid2 = fork();
  
  if(pid2 == 0){
    close(1);
    dup(p1_to_p2[1]);
    close(p1_to_p2[0]);
    close(p1_to_p2[1]);
    
    execv(full_path, arguments);
  }

  close(p1_to_p2[0]);
  close(p1_to_p2[1]);

  waitpid(pid, &status, 0);
  waitpid(pid2, &status, 0);
		
		
		getTime(2);	// get final time
	}		


// ------------------------------------------------------------------------- //

int parsePath(char *cmd) {		// parses PATH environment variable

	char *path = strdup(getenv("PATH"));
	const char *const delim = ":";

	// For fully correct results, you must allocate the save_pointer
	// to be the length of the string you'll parse.
	char *save = (char *) malloc(strlen(path));

	// Here, we save the position of the save pointer so that we can free it later.
	char *path_start_addr = path;	// start memory address for path pointer
	char *save_start_addr = save;	// start memory address for save pointer
	char *token; 		// useful string after tokenization

	// Iterate through each token	
	for(token = strtok_r(path, delim, &save); token != NULL; token = strtok_r(NULL, delim, &save)){
		if(check(token, cmd)) {
			return 1;	// it found something
			break;
		}
	}
	
	// free memory
	free(save_start_addr);
	free(path_start_addr);
	return 0;	// didn't find anything
	
}	// end of parsePath

int BuiltIn(char * cmd) {	// returns 1 if built in command was found, 0 if not
	
	getTime(1);
	getTime(2);
	
	if(!strcmp(cmd,"echo")) {
		echo();	// call echo
		return 1;
	}
	else if(!strcmp(cmd,"cd")) {
		cd();	// call cd
		return 1;
	}
	
	else if(!strcmp(cmd,"viewproc")) {
		viewproc();	// call viewproc
		return 1;
	}

	return 0;	// not a built-in command
}

// ------------------------------------------------------------------------- //

void viewproc() {	// implements viewproc built-in command
	int c;	// to contain the character
	if (check("/proc",arguments[1]) )  { // check if file exists
		printf("%s was found in /procs.\n",arguments[1]);
		FILE *myfile;	// create FILE object
		myfile=fopen(full_path,"r");	// open file for reading
		while(c != EOF) {
			//puts("Getting character");
			c= fgetc (myfile);
			if (c != EOF)	// print every character except for EOF (int)
				printf("%c",c);	// print character
		}
		fclose(myfile);	// close file
		printf("\n");
	}
	else
		printf("error: %s is not in /procs.\n",arguments[1]);
}	// end of viewproc

// ------------------------------------------------------------------------- //
void getTime(int i) {
	
	time_t current_time;
	struct tm * time_info;
	char timeString[9];  

	time(&current_time);
	time_info = localtime(&current_time);

	strftime(timeString, sizeof(timeString), "%H:%M:%S", time_info);
	
	if (i==1)
		time1 = strdup(timeString);
	else if (i==2)
		time2 = strdup(timeString);

}

char * getStatus(char * cmd) {
	char * status;
	
	if (check("/proc",cmd))	// check PID folder under proc
		status="Running";
	else
		status="Stopped";
	
	return status;

}
// ------------------------------------------------------------------------- //
void addHistory(char * cmd2) {

	char * cmd = strdup(cmd2);	
	command[size2]=cmd;
	startTime[size2]=time1;
	endTime[size2]=time2;
	status[size2]=getStatus(cmd);
	size2++;	// increase size
}

void displayHistory()	{
	int z=0;
	
	for(z=0;z<size2;z++) {
		printf("%i \t %s \t %s \t %s \t \t \t %s \n", z, startTime[z], endTime[z], command[z], status[z]); 
	}

}


int main(void)
{

	while(1) {
		bg=0;	// assume that the command will not be executed in the background
		getcwd(pwd,127);	// update current working directory
		prompt();		// call our prompt
		
		if(getArgs(arguments) !=0)
			puts("error 46: malformed i/o redirection");
		
		else if(!justcmd);	// don't do anything since user didn't type anything
		
		else if(!strcmp(justcmd,"exit")) {	// exit the shell
			 puts("Exiting the shell");
			 return 0;
		}
		
		else if(numpipes==0) {
		
			if (BuiltIn(justcmd));	// check and run if built-in 
			
			else if(!strcmp(justcmd,"history"))
				displayHistory();
			
			else if(parsePath(justcmd)) 	{	// path was found
				if(!strcmp(justcmd,"which") )
					which();	// if the command was "which"
				else
					executecmd(full_path);	// execute command with the path
			}
			else	// if nothing else worked, probably the cmd was not found
				printf("%s: command not found.\n",justcmd);
			
		}
		
		else if(numpipes!=0) {
			getPipes();	// get the pipes
			parsePath(justcmd);	// for the first argument
			executecmdPipes(numpipes);	// execv for pipes
			addHistory(str);
		}
		
		else	// if nothing else worked, probably the cmd was not found
			printf("%s: command not found.\n",justcmd);
		
		if(justcmd)	// check if the command was empty
			addHistory(str);
		
		
	}
	
	
	
   return 0;
}	// end of Main
